package ks.client.game.solitaire;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JPanel;

import ks.client.game.GameFrame;
import ks.client.game.GameInformation;
import ks.client.gamefactory.IUpdateStatus;
import ks.client.gamefactory.PlayerJPanel;
import ks.client.timer.TimerPanel;
import ks.client.timer.TimerUpdate;
import ks.common.games.Solitaire;
import ks.common.view.Container;
/**
 * Information known for a Solitaire variation.
 * 
 * @author George Heineman
 */
public class SolitaireGameInformation extends GameInformation implements IUpdateStatus {

	// attributes of a solitaire game.
	int time;
	boolean undo;
	boolean newHand;
	
	/** Solitaire container within which we are being played. */
	Container solitaireContainer;
	
	/** Active game being played. */
	Solitaire activeGame;
	
	/** Controls for solitaire. */
	JPanel controlPanel;
	
	/** New Hand button. */
	JButton newHandButton;
	
	/** Player ranking. */
	PlayerJPanel playerRanking;
	
	/** Stats on game (solitaire only). */
	int numNewHands = 0;
	int numWins = 0;
	int gameTime = 0;
	
	/** Score since last 'init' or 'new Hand'. Enables incremental count. */
	int accumulatedScore;

	/** Score for the current game only. */
	int partialScore = 0;
	
	
	/** Timer panel. */
	TimerPanel timerPanel;
	
	/** Enclosing GameFrame. */
	GameFrame frame;
	
	/**
	 * Options for solitaire include:
	 * 
	 * undo<boolean>
	 * newHand<boolean>
	 * time<int>
	 * 
	 * @param options
	 * @param gameOptions
	 */
	public SolitaireGameInformation(Properties options, Properties gameOptions) {
		super(options);
		
		// set game time.
		String gameTimeS = gameOptions.getProperty("time");
		if (gameTimeS == null) {
			gameTime = 300;
		} else {
			try {
				gameTime = Integer.valueOf(gameTimeS);
			} catch (Exception e) {
				e.printStackTrace();
				gameTime = 300;
			}
		}
	}

	/**
	 * Start the game within the given frame.
	 * 
	 * @param frame
	 * @return
	 */
	public boolean start(GameFrame frame) {
		this.frame = frame;
		frame.setVisible(true);
		
		Class<?> c;
		try {
			c = Class.forName(game);
			activeGame = (Solitaire) c.newInstance();
			activeGame.setSeed(getSeed());
			
			// Get Game Container and initialize game within it. Make sure
			// that container is as large as preferred size of solitaire...
			java.awt.Dimension d = activeGame.getPreferredSize();
			
			((Container)getGameContainer()).setBounds(0, 0, Math.max(764, d.width), Math.max(512, d.height));
			
			getGameContainer().setPreferredSize(d);
			getGameContainer().setMinimumSize(d);
			frame.setSize(new Dimension(d.width + 80, d.height + 200));
			
			// initialize once bounds are computed, otherwise have problems redrawing extra regions.
			if (!((Container)getGameContainer()).initialize(activeGame)) {
				return false;
			}
			
			// start the game.
			timerPanel.start(gameTime);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * Returns the actual GUI element within which the game plays.
	 */
	@Override
	public java.awt.Component getGameContainer() {
		if (solitaireContainer == null) {
			solitaireContainer = new Container();
			solitaireContainer.setUpdateStatus(this);
			solitaireContainer.setName("Container");
			solitaireContainer.setBackground(new java.awt.Color(0,138,0));
			solitaireContainer.setSize(764, 512);
	
			// wire up mouse listeners (both motion and regular). All mouse events
			// are first sent to the container, and then to the game itself.
			solitaireContainer.addMouseListener (new MouseListener() {
				
				@Override
				public void mouseReleased(MouseEvent e) {
					solitaireContainer.processMouseEvent(e);
				}
				
				
				@Override
				public void mousePressed(MouseEvent e) {
					solitaireContainer.processMouseEvent(e);
				}
				
				@Override
				public void mouseExited(MouseEvent e) {
					solitaireContainer.processMouseEvent(e);
				}
				
				@Override
				public void mouseEntered(MouseEvent e) {
					solitaireContainer.processMouseEvent(e);
				}
				
				@Override
				public void mouseClicked(MouseEvent e) {
					solitaireContainer.processMouseEvent(e);
				}
			});
			
			solitaireContainer.addMouseMotionListener (new MouseMotionListener() {
				
				@Override
				public void mouseMoved(MouseEvent e) {
					solitaireContainer.processMouseEvent(e);
					
				}
				
				@Override
				public void mouseDragged(MouseEvent e) {
					solitaireContainer.processMouseEvent(e);
				}
			});
		}
		
		return solitaireContainer;
	}

	/**
	 * Returns the actual GUI controls for the game.
	 * 
	 * Timer/New Hand/Player Ranking.
	 */
	@Override
	public JPanel getGameControls() {
		if (controlPanel == null) {
			controlPanel = new JPanel();
				
			GroupLayout layout = new GroupLayout(controlPanel);
			controlPanel.setLayout(layout);
			layout.setAutoCreateGaps(true);
			layout.setAutoCreateContainerGaps(true);
			
			timerPanel = new TimerPanel();
			timerPanel.setMaximumSize(new Dimension (100, 80));
			playerRanking = getPlayerRanking();
			newHandButton = new JButton ("New Hand");
			newHandButton.addActionListener (new ActionListener() {
	
				@Override
				public void actionPerformed(ActionEvent e) {
					((Container)getGameContainer()).forceNextHand();
				}
				
			});
			
			timerPanel.setMaster(new TimerUpdate() {

				int pastScore = -1;
				
				@Override
				public void minorTick() {
					// do nothing
					if (accumulatedScore+partialScore == pastScore) { return; }
					pastScore = getScore();
					
					callback.update(frame.getTableNumber(), pastScore,
							"numWins=" + numWins + ",numNewHands=" + numNewHands,
							false);
				}

				@Override
				public void timerExpired() {
					// turn off the action in the game.
					((Container)getGameContainer()).setActive(false);
					newHandButton.setEnabled(false);
					
					callback.update(frame.getTableNumber(), accumulatedScore+partialScore,
							"numWins=" + numWins + ",numNewHands=" + numNewHands,
							true);
				}
				
			});
			
			timerPanel.setMaximumSize(new Dimension(120, 85));

			layout.setHorizontalGroup(layout.createSequentialGroup()
					.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
							.addComponent(timerPanel)
							.addComponent(newHandButton))
					.addComponent(playerRanking)
					);
			
			layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
					.addGroup(layout.createSequentialGroup()
							.addComponent(timerPanel)
							.addComponent(newHandButton))
					.addComponent(playerRanking)
							);
							
		}
		
		return controlPanel;
	}

	/**
	 * Return the player ranking object.
	 *  
	 * @return
	 */
	public PlayerJPanel getPlayerRanking() {
		if (playerRanking == null) {
			playerRanking =	new PlayerJPanel();
		}
		
		return playerRanking;
	}
	
	public void incrementNumNewHands() {
		numNewHands++;
		
		// make sure to set highwater mark.
		accumulatedScore += partialScore;
		partialScore = 0;
	}

	public void incrementNumWins() {
		numWins++;
		
		accumulatedScore += partialScore;
		partialScore = 0;
	}

	@Override
	public void updateScore(int score) {
		this.partialScore = score;
			
		getPlayerRanking().updatePlayerInfo(fullName(player), numWins, accumulatedScore + partialScore);
	}

	@Override
	public void initialize() {
		// NOP method will be removed in future release.
	}

	@Override
	public void stop() {
		timerPanel.stop();
	}
	
	/**
	 * We need to update player rankings. First let superclass handle its
	 * updates and then we take over.
	 */
	@Override
	public void setPlayers(ArrayList<String> order, Properties playerIDs) {
		super.setPlayers(order, playerIDs);
		
		for (Object key : playerIDs.keySet()) {
			String k = (String) key;
			
			String fullName = fullName(k);
			getPlayerRanking().addUser(fullName);
			getPlayerRanking().updatePlayerInfo(fullName, 0, 0);
		}
	}

	/**
	 * When receiving GameResponse from server, this extracts all
	 * relevant information and updates local player ranking table
	 * appropriately.
	 * <p>
	 * In solitaire, updatedGame properties are of the form:
	 * 
	 * "numWins=3,numNewHands=2"
	 * 
	 * Extract the numWins from the game property.
	 */
	@Override
	public boolean updateScores(Properties updatedScores, Properties updatedGame) {
		for (Object key : updatedScores.keySet()) {
			String k = (String) key;
			
			String fullName = fullName(k);
			
			int score = 0;
			String str = updatedScores.getProperty(k);
			try {
				score = Integer.valueOf(str);
			} catch (Exception e) {
				System.err.println("Unable to update score for player " + fullName + ": score is not a string:" + str);
			}
			
			int numHandsWon = 0;
			String game = (String) updatedGame.getProperty(k);
			if (game != null) {
				try {
					StringTokenizer st = new StringTokenizer(game,",");
					while (st.hasMoreTokens()) {
						String pair = st.nextToken();
						int idx = pair.indexOf('=');
						String name = pair.substring(0, idx);
						String val = pair.substring(idx+1);
						
						if (name.equals ("numWins")) {
							numHandsWon = Integer.valueOf(val);
							break;
						}
					}
				} catch (Exception e) {
					System.err.println("Unable to update game for player " + fullName + ": game info is:" + game);
				}
			}
			
			// all the above done is to make this work.
			getPlayerRanking().updatePlayerInfo(fullName, numHandsWon, score);
		}
		
		return true;
	}
	
	@Override
	public boolean activateTurn(String playerID) {
		System.out.println("ignoring activateTurn in soltaire game");
		return false;
	}

	@Override
	public boolean makeTurn(String playerID, String moveString) {
		System.out.println("Not expecting to make turn in solitaire-based games");
		return false;
	}

	@Override
	public boolean skipTurn(String playerID) {
		System.out.println("Not expecting to make turn in solitaire-based games");
		return false;
	}

	/**
	 * Because we override, we have to take care to invoke our
	 * parent's method because it manages player info.
	 */
	@Override
	public boolean requestLeave(String playerID) {
		boolean rc = getPlayerRanking().disablePlayer(fullName(playerID));
		rc = rc & super.requestLeave(playerID);
		
		return rc;
	}

	/**
	 * A player's score is based on secure points (accumulated score)
	 * plus their partial score for the current game. Whenever the player
	 * secures a new game, the partialscore points are locked in. until then
	 * they can be reduced by player undo.
	 *  
	 * Part utility method, part helper method for testing.
	 * @return   current score of the game, based on accumulatedScore and partialScore.
	 */
	public int getScore() {
		return accumulatedScore + partialScore;
	}

	/** Helper method for testing. */
	public Solitaire getActiveGame() {
		return activeGame;
	}

	
}
